--[[ A library of functions that extend 'table'. ]]
module(..., package.seeall)


local function pretty_item(k, v, recursion, cache)
    k = tostring(k)
    if recursion > 0 and type(v) == "table" then
        v = table.pstring(v, recursion - 1, cache)
        v = string.gsub(v, "\n", "\n    ")
    else
        v = tostring(v)
    end
    return string.format("\n    %s = %s", k, v)
end

function table.pstring(t, recursion, cache)
    --[[ Build a pretty string representation of the argument table. ]]
    recursion = recursion or 0
    cache = cache or {}  -- cache prevents circular references from blowing up the stack
    if cache[t] then
        return string.format("<<<%s>>>", tostring(t))
    end
    cache[t] = true
    local parts = {"{", tostring(t)}
    for k, v in pairs(t) do
        parts[#parts + 1] = pretty_item(k, v, recursion, cache)
    end
    if #parts > 2 then
        parts[#parts + 1] = "\n"
    end
    parts[#parts + 1] = "}"
    return table.concat(parts)
end

function table.pprint(t, recursion)
    io.write(table.pstring(t, recursion), "\n")
end

function table.repr(t)
    local meta = getmetatable(t)
    if meta ~= nil and rawget(meta, "__tostring") ~= nil then 
        -- if the table has a metatable and a __tostring() metemethod, then use it
        return tostring(t)
    end
    
    -- otherwise build a string representation of the table's contents
    local str_fmt = string.format
    local parts = {"{"}
    for k, v in pairs(t) do
        parts[#parts + 1] = str_fmt("%s=%s", tostring(k), tostring(v))
        parts[#parts + 1] = ", "
    end
    parts[#parts] = "}"
    return table.concat(parts)
end

function table.clear(t)
    --[[ Remove all entries from a table. ]]
    for k, v in pairs(t) do
        t[k] = nil
    end
end

function table.equals(t1, t2)
    --[[ Check if two tables have exactly the same keys and values. ]]
    t2 = table.copy(t2)
    for k, v in pairs(t1) do
        if t2[k] ~= v then
            return false
        end
        t2[k] = nil
    end
    return next(t2) == nil
end

function table.key(t, v)
    --[[ Given a value, returns the key associated to that value. ]]
    for k, w in pairs(t) do
        if w == v then return k end
    end
    error(string.format("%s not found in %s", v, t))
end

function table.copy(t1, t2)
    --[[ Create a *shallow* copy of the argument table 't1'. If a second table 't2' is supplied, 
    the copy is made into 't2'. ]]
    local clone = t2 or {}
    for k, v in pairs(t1) do
        clone[k] = v
    end
    return clone
end

function table.inverse(t1, t2)
    --[[ Creates an inverse table, i.e. one that maps values from the original table to their
    respective keys (i.e., keys become values and values become keys). ]]
    local inverse = t2 or {}
    for k, v in pairs(t1) do
        inverse[v] = k
    end
    return inverse
end

function table.pop(t, k, v)
    --[[ Returns the value in table 't' associated with key 'k', also removing the key from the 
    table. Returns the default value 'v' if the key is not found in the table, or raises an error 
    if a default was not specified. ]]
    local u = t[k]
    if u == nil then  -- we can't use 'u = t[k] or v' here because t[k] may be 'false'
        if v == nil then
            error(string.format("key '%s' not found in table", k))
        end
        u = v
    end
    t[k] = nil
    return u
end

function table.xget(t, ...)
    for _, attr in ipairs{...} do
        t = t[attr]
    end
    return t
end

function table.xset(t, ...)
    local args = {...}
    local n = #args
    assert(n >= 2, "insufficient arguments")
    for i = 1, n - 2 do
        t = t[args[i]]
    end
    t[args[n-1]] = args[n]
    return args[n]
end

function table.xdel(t, ...)
    return table.xset(t, ..., nil)
end

function table.keylist(t)
    local keys = {}
    for k, _ in pairs(t) do
        table.insert(keys, k)
    end
    return keys
end

function table.valuelist(t)
    local values = {}
    for _, v in pairs(t) do
        table.insert(values, v)
    end
    return values
end

function table.pack(...)
    return {...}
end

table.unpack = unpack

